#!/usr/bin/python
###############################################################################
#Programmers: Trebol-a, info@trebol-a.com                                     #
#             Danil Dotsenko, dd@accentsolution.com                           #
#                                                                             #
#                                                                             #
#    Copyleft 2006,2007 Danil Dotsenko                                        #
#                                                                             #
#    This program is free software; you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation; either version 2 of the License, or        #
#    (at your option) any later version.                                      #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program; if not, write to the Free Software              #
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA #
#                                                                             #
###############################################################################

from __future__ import division
import karamba
import os
from commands import getoutput
import random
import glob
import re
from zipfile import is_zipfile
from StringIO import StringIO
from gettext import GNUTranslations
import localconf # this is a custom wrapper for ConfigParser. Should be in local dir.

def debug():
	return True

###########################################################################
# All applet settings go here, and are accessible from everywhere.
###########################################################################
class configClass:
	'''
	An all-encoupasing, application-wide configuration storage location.
	This will be shared even between concurrently running themes.
	This is the equivalent of root directory of Windows registry.
	'''
	def __init__(self):
		self.common={
			'name':'A-Foto',
			'version':"v1.5.8, February 10, 2006",
			'author':"Trebol-a (info@trebol-a.com)\nDanil (Suslik) Dotsenko (dd@accentsolution.com)",
			# incriment this every time there is update in /frames or /locale folders and you want to overwrite them in ~/.superkaramba
			'temasversion':None, # loaded from INI
			'localeversion':None, # loaded from INI
			'homepath':None,
			'fileLimit':None, # loaded from INI
			'forever':60000000, # 1000 Minutes. we use this in "do not update the picture in single mode" as the update interval.
			'temasPath':'', # automatically generated on every 1st start
			'localePath':'', # automatically generated on every 1st start
			'frameParts':["aa","ab","ac","ba","bc","ca","cb","cc"], # used for filenames. "s_" added for shadow, "t_" added for frame
			'fileFilter':re.compile(".+\.(jpg|jpeg|png|gif|bmp)$",re.IGNORECASE),
			'supported':[], # loaded from INI and converted to [] - should be an array
			'order':[], # loaded from INI and converted to [] - should be an array containig INI file group names.
			'active':[], # actually running instances using INI file group names.
			'cleanStart':False,
		}
		self.ini = None # will be a holder of INI style config file contents and associated methods
		self.instance = {} # this will be populated by settings of each consecutive theme. See instanceClass class bellow
		self.plugins = {}
	
	def encode(self,string):
		'''
		This is a helper function to convert pure unicode strings into locale-specific utf8 or utf16.
		This is needed for some python functions, like popen, that cannot handle pure unicode.
		'''
		# it the future this may try to catch the actual locale and use that,
		# in the mean time, if it's unicode, you get utf8
		if type(string) == type(u''): # unicode
			return string.encode('utf8')
		else:
			return string
	
	def pushSettingsDown(self):
		'''
		This function packages and deposits COMMON afoto settings at INI object.
		We dont' save it. Issue something like appConfig.ini.save() when ready.
		'''
		toSave = {
			'temasversion':None,
			'localeversion':None,
			'fileLimit':None,
			'order':None,
			}
		for key in toSave.keys():
			toSave[key]=self.common[key]
		
		self.ini.set('common',toSave)
	
	def pullSettingsUp(self):
		'''
		This function pulls relevant entries from INI object. We don't load INI.
		Issue something like appConfig.ini.load(filename) before this.
		Note we pull ALL settings, push up only selected settings.
		'''
		self.common.update(self.ini.get('common'))
		# if only one value was saved in an entry, it may return as string, not array, so
		toArray = [
			'supported',
			'order',
			]
		for key in toArray:
			if type(self.common[key]) == type(''): # string
				self.common[key] = [self.common[key]]
		
		# now, we are trying to catch our own mistakes and lack of thread-save config interface.
		# sometimes, when SUperKaramba is exiting, it may ask many afoto instances to close at the same time
		# in that case, simultaneous writing of configs may result in screwed up "order" entry in afiti.ini
		# we are going to pick up lost instance settings and add them to the end of line. - better last than lost.
		sections = self.ini.cfgObject.keys()
		section = sections.__len__() - 1
		if debug():
			print "pullSettingsUp: sections are ", sections
		while section > -1:
			if sections[section][:9] != 'instance_':
				sections.__delitem__(section)
			section = section - 1
		if debug():
			print "pullSettingsUp: sections part 2 are ", sections
		if sections.__len__() > self.common['order'].__len__():
			for section in self.common['order']:
				try:
					sections.remove(section)
				except:
					pass
			if debug():
				print "pullSettingsUp: sections part 3 are ", sections
			self.common['order'].extend(sections)
	
	def demoteInstance(self, name):
		'''
		This function moves the Name of instance up or down the Order string.
		The new place will be right after all "Running" instances.
		This is usually called when we close this instance.
		(We also pushSettingsDown, but don't save INI file.)
		'''
		remaining = self.common['order']
		# lets remove all running out of the line.
		for iname in self.common['active']:
			try:
				remaining.remove(iname)
			except:
				pass
		self.common['active'].remove(name)
		self.common['order'] = self.common['active'] + [name] + remaining
		
		self.pushSettingsDown()
		
		# returns instance's new place in the Order.
		# if it is 0 == it was last running instance.
		return self.common['order'].index(name)
	
	def newInstanceSettings(self):
		'''
		This function looks at list of currently running instances and compares
		it to order list.
		- if all instance on order are already runing, we create a  completely
		  new one and put it on both: "order" and "running" list
		- if there are some instances on "order" list that are not used yet, we
		  read that set of settings.
		In both cases we return the _bare minimum_ of settings.
		We add the instance NAME, not widget reference to the active, order,
		but don't push it up to INI object. Use *.instance[widget].pushSettingsDown()
		followed by *.ini.save() to actually save the settings in INI file.
		'''
		# This part is THREAD-UNSAFE. I am hoping no one will find out.
		if self.common['active'].__len__() < self.common['order'].__len__():
			# this means there are some old instance settings that we can use
			# lets remove what we already run from the order line.
			remaining = self.common['order']
			# lets remove all running out of the line.
			for iname in self.common['active']:
				if remaining.__contains__(iname):
					remaining.remove(iname)
			nextInstance = remaining[0]
			# now, the usefull part.
			self.common['active'].append(nextInstance)
			nextSettings = self.ini.get(nextInstance)
		else:
			# if we are here - we used up all "old" instance settings and have to create completely new set.
			nextInstance = "instance_" + str(self.common['active'].__len__())
			self.common['active'].append(nextInstance)
			self.common['order'].append(nextInstance) # this needs to be deposited in INI object
			self.pushSettingsDown() # but that doesn't save the INI file yet. Do it later after the new instance is done with its configs and tests the foto.
			nextSettings = {
				'associatedName':nextInstance,
				'currentPath':self.common['temasPath']+'inicio.png', # should always be local
				'resourcePath':'file://'+self.common['temasPath']+'inicio.png', # should always be URL-like
				}
		return nextSettings

class instanceClass:
	'''
	One copy of this class will be assigned to each consecutive theme instance.
	'''
	def __init__(self):
		self.cfg = {
			'associatedName':None, # Name of config group in INI file with which this entry is associated.
			'currentPath':None, # we can't set path here because we don't know where default picture is in relation to us.
			'resourcePath':None, # path to the folder or service containing the picture. These look like URL's with the type part dictating the type of resource
			# at this time, the following types are envisioned: file:// http:// flickr:// and each has a special handling method.
			'update':0, # means "change picture or get new version of picture" - if true = slideshow or webcam mode, if false = single mode.
			'updateinterval':120000, # 2 minutes. Used for slideshow mode.
			'frame':"a-foto", # frame theme
			'maxsize':180, # limits BOTH! height AND width of FOTO element. Frame will start OUTSIDE of maxsize
			'shadowsize':18,
			'limitlong':0, # we can't use True / False as our configparser turns them into strings.
			'locationX':200,
			'locationY':200,
			'width':200,
			'height':200,
			}
		self.ptr = {
			'foto':None,
			'sombra':[],
			'estilo':[],
			'maxsize':None,
			'shadowsize':None,
			'updateinterval':None,
			'frame':None,
			}
		self.menu = {}
		self.flag = {
			'hitFileLimit':False, # this is used for special case when dir contains too many files, which may cause 100% CPU durring search and infinite loops.
			'drawFrame':True, # this is an accelleration flag. if false, skipping the drawing part.
			'drawShadow':True, # this is an accelleration flag. if false, skipping the drawing part.
			'delayed_action':None,
			'lastorientation':1,
			}

	def pushSettingsDown(self):
		'''
		pushSettingsDown()
		This function will organize and migrate the theme-related settings into
		ini-file object / repository. We do not save the INI file yet, just
		deposit the settings there. This is useful for making all instances
		save the settings to INI object and save it with last exiting instance.
		'''
		appConfig.ini.set(self.cfg['associatedName'],self.cfg)

	def makeSettingsProper(self):
		'''
		Our INI backend has a nasty habit of turning everything into strings, so
		we need to convert relevant entries back to needed format after each read from INI backend.
		'''
		toInt = [
			'shadowsize',
			'limitlong',
			'locationY',
			'update',
			'width',
			'maxsize',
			'locationX',
			'updateinterval',
			'height',
			]
		for key in toInt:
			self.cfg[key] = int(self.cfg[key])

	def pause(self,widget,value=True):
		'''
		this helper function will set a or remove pause in slideshow
		If Value is set to False, we UnPause.
		If it's set to true, or we call function without argument Value, the applet is "Paused"
		'''
		if value: # if true pausing
			karamba.changeInterval(widget, appConfig.common['forever'])
			self.cfg['update'] = 0
			
			#this takes care of play / pause button in the menu
			karamba.removeMenuItem(widget, self.menu['menu1'], self.menu['pause'])
			self.menu['pause'] = karamba.addMenuItem(widget, self.menu['menu1'],  _("Resume slideshow") , "player_play")
		else: # unpausing
			karamba.changeInterval(widget, self.cfg['updateinterval'])
			self.cfg['update'] = 1
			
			#this takes care of play / pause button in the menu
			karamba.removeMenuItem(widget, self.menu['menu1'], self.menu['pause'])
			self.menu['pause'] = karamba.addMenuItem(widget, self.menu['menu1'],  _("Pause slideshow") , "player_pause")
		
	def unpause(self,widget, value=True):
		'''
		this helper function will remove a pause in slideshow
		'''
		self.pause(widget, not value)

	def setNewInterval(self,widget,newInterval=None):
		'''
		setInterval(widget,[newInterval])
		this helper function will set the interval for whole theme (applet) update.
		If no value is assigned to newInterval, it just uses what is already set in updateinterval var
		This is useful for cases when we are only starting the theme and just want to activate what is in the settings.
		'''
		if newInterval:
			karamba.changeInterval(widget, newInterval)
			self.cfg['updateinterval'] = newInterval
		else:
			karamba.changeInterval(widget, self.cfg['updateinterval'])
		self.unpause(widget)

###########################################################################
# A-Foto has 3 levels of start-up functions.
# 1. "installUpdate()"
#     if skz package is new on this system, it prepares/unpacks all needed
#     files in ~/.superkaramba/afoto.
#     (at this point functionality of installUpdate() is rolled into commonConfig())
# 2. "commonConfig()"
#     Test and loads the config file and initiates variety of common classes.
#     This is done only with start of 1st instance of a-foto
# 3. "instanceConfig()"
#     loads settings for a new a-foto instance. It doesn't and shouldn't care if it's first or not.
# 3.5 "tryNextImage()"
#     It is a helper function that comes in play on widget start and updates.
#
def commonConfig(widget):
	global appConfig, _

	cleanStart = False

	###########################################################################
	# Here we determine if we run from *.skz file, and if true, extract it and use new extracted path.
	# Else, we use theme's folder for everything.
	###########################################################################
	if is_zipfile(karamba.getThemePath(widget)):
		# Note! It is important to understand this section for SKIN AUTHORS and TRANSLATORS!!!
		# Before extracting everything, we will check the config file 
		# YOUR UPDATED SKINS / TRANSLATIONS WILL NOT BE EXTRACTED
		# if the variables above in this file are not updated to higher date/value.
		homepath = os.environ['HOME']+'/.superkaramba/afoto/'
		if debug():
			print "commonConfig: this is a zipfile"
		if not os.path.exists(homepath+'running'):
			cleanStart = True
		else:
			if debug():
				print "commonConfig: other instance present flag file is found"
			# another instance is already running - abort.
			message = "You may see this message for one of two reasons:\nA-Foto is already running and you are trying to start another A-Foto instance in an incompatible way, or\nSuperKaramba crashed and left some of AFoto pieces around.\nPress CANCEL to stop loading this copy of A-Foto. Use main SuperKaramba interface to start additional A-Foto instances.\nPress CONTINUE If you are sure there are no other running A-Foto instances."
			myCommand = 'kdialog --caption "'+appConfig.common['name']+'" --title "Error" --warningcontinuecancel "'+message+'" ; echo $?'
			if not int(getoutput(myCommand)): # Cancel Button = code 2. Continue Button = code 0
				cleanStart = True

		if cleanStart:
			if os.path.exists(homepath) == False:
				os.makedirs(homepath)
			toExtract = []
			# this is where we load the ini-based config file from ~/.superkaramba/afoto folder
			# if exists, we check the version of installed temas and extract them if needed.
			# NEED_WORK this is a mess. will rework this when have time.
			if appConfig.ini.load(homepath+'afoto.ini'): # True if file was read.
				# if it exists, we need to compare it to the one packaged in SKZ.
				defaults = karamba.readThemeFile(widget, 'afoto.ini')
				defaultsTemaVer = re.findall('(?<=temasversion = )[0-9]+', defaults)
				defaultsLocaleVer = re.findall('(?<=localeversion = )[0-9]+', defaults)
				if defaultsTemaVer > appConfig.ini.get('common','temasversion'):
					toExtract.append('temas/*')
					appConfig.ini.set('common','temasversion', defaultsTemaVer)
					appConfig.ini.delayed_action("save")
				if defaultsLocaleVer > appConfig.ini.get('common','localeversion'):
					toExtract.append('locale/*')
					appConfig.ini.set('common','localeversion',defaultsLocaleVer)
					appConfig.ini.delayed_action("save")
			else: # this means afoto.ini file was not found => completely new install
				toExtract.append('afoto.ini')
				toExtract.append('temas/*')
				toExtract.append('locale/*')
				appConfig.ini.delayed_action("load", homepath+'afoto.ini')
			# now, we know what to extract
			if len(toExtract):
				myCommand = ['unzip', '-o', karamba.getThemePath(widget)]
				myCommand.extend(toExtract)
				myCommand.append('-d')
				myCommand.append(homepath)
				os.spawnvp(os.P_WAIT, 'unzip', myCommand) # this sommand holds the progress until extracting is done.
				appConfig.ini.delayed_action()
			appConfig.pullSettingsUp()
			appConfig.common['temasPath'] = homepath+"temas/"
			appConfig.common['localePath'] = homepath+"locale/"	
			appConfig.common['homepath'] = homepath
			appConfig.pullSettingsUp() # retrieves common"Order" and common"Supported" out of INI object
			# placing the "already running" flag on filesystem
			a = open(homepath+'running', 'w')
			a.close()
			cleanStart = True
	else: # this means we do NOT run from SKZ file
		# this option uses the folder you run afoto from and sets that as the path for all items
		# NOTE! even config file is localised to the current folder!
		# This behavior is beneficial for troubleshooting and testing, as it will not mess up the "working" settings in ~/.superkaramba/afoto
		homepath = karamba.getThemePath(widget)
		if debug():
			print "commonConfig: this is an unpacked folder"
		if not os.path.exists(homepath+'running'):
			cleanStart = True
		else:
			if debug():
				print "commonConfig: other instance present flag file is found"
			# another instance is already running - abort.
			message = "You may see this message for one of two reasons:\nA-Foto is already running and you are trying to start another A-Foto instance in an incompatible way, or\nSuperKaramba crashed and left some of AFoto pieces around.\nPress CANCEL to stop loading this copy of A-Foto. Use main SuperKaramba interface to start additional A-Foto instances.\nPress CONTINUE If you are sure there are no other running A-Foto instances."
			myCommand = 'kdialog --caption "'+appConfig.common['name']+'" --title "Error" --warningcontinuecancel "'+message+'" ; echo $?'
			if not int(getoutput(myCommand)): # Cancel Button = code 2. Continue Button = code 0
				cleanStart = True
		
		if cleanStart:
			if debug():
				print "commonConfig: cleanStart"
			appConfig.common['temasPath'] = homepath+"temas/"
			appConfig.common['localePath'] = homepath+"locale/"
			appConfig.common['homepath'] = homepath
			appConfig.ini.load(homepath+'afoto.ini')
			appConfig.pullSettingsUp() # retrieves common"Order" and common"Supported" out of INI object
			# placing the "already running" flag on filesystem
			a = open(homepath+'running', 'w')
			a.close()
			cleanStart = True
	
	appConfig.common['homepath'] = homepath
	
	if debug():
		print "commonConfig: appConfig.common is ", appConfig.common
	
	###########################################################################
	# This activates gettext-based translation services
	#   from now on, use _('translatable text') instead of 'UN-translatable text'
	###########################################################################	
	
	if cleanStart:
		try:
			lang = karamba.userLanguages(widget)
			# This should give us a nice list (array) with all preferred languages.
			# in middle ages userLanguage existed but returned a string with only one language.
			# we don't like that. See bellow
		except:
			lang = 'not supported'
		
		if type(lang) != type([]): # not array, need to scrub kdeglobals manually
			tempOut = getoutput('more ~/.kde/share/config/kdeglobals | grep Language')
			lang = re.split(':',tempOut[9:]) # this may give several languages as an array (ex: ['ru','en_US'])
		
		languageFile=None
		i = 0
		while i < lang.__len__() and languageFile==None:
			if os.path.exists(appConfig.common['localePath']+lang[i]+".po"):
				# OK, we will try to be nice and compile nonexisting or stale .mo's
				# This should be useful for translators - no need to compile .mo's, just translate the .po and rerun afoto.
				# DO NOT SHIP THE THEME WITHOUT .mo's. Users are unlikely to have msgfmt installed.
				if os.path.exists(appConfig.common['localePath']+lang[i]+".mo") == False:
					msgfmtPath = re.compile('/.*msgfmt$').match(getoutput('which msgfmt'))
					if msgfmtPath:
						myCommand = "msgfmt "+appConfig.common['localePath']+lang[i]+".po -o "+appConfig.common['localePath']+lang[i]+".mo"
						getoutput(myCommand)
				languageFile=appConfig.common['localePath']+lang[i]+".mo"
			i = i + 1
		# thx to Matthew (dmbkiwi@yahoo.com) for showing the right way of doing this.
		# modified excerpt from LiquidWeather sources
		if(languageFile == None):
			_ = lambda(x): x
		else:
			# s = karamba.readThemeFile(widget, languageFile) # this function does not wotk on fully-formed URLs
			if os.path.exists(languageFile):
				filePointer = open(languageFile,'r')
				fileContents = filePointer.read()
				filePointer.close()
			if fileContents.__len__() > 0:
				gt = GNUTranslations(StringIO(fileContents))
				_ = gt.ugettext
			else:
				_ = lambda(x): x
		# end of excerpt
	
	return cleanStart

###########################################################################
# SCANNING FOR PLUGINS FOR VARIOUS PATHS SUPPORT
###########################################################################
def scanPlugins(customPath=None):
	global appConfig
	'''
	This function looks at the folder we decided to treat as home folder (appConfig.common['homepath'])
	and scans the plugins folder for available plugins. Plugins are identified by ini file.
	Initially we will support only accompanying files in the same directory. 
	Later, we will think about adding some flag to ini file to point to a separate dir for plugin code.
	'''
	# 1. Get listing of ini files in that dir.
	# 2. import the py's one by one, pulling things out of them in the process.
	# things to pull out
	# - "dropped path" string filter 
	# - will need to have a detected > internal path conversion function in your code
	# - internal address storage URI moniker (file, http, flickr, picasa etc.)
	# - function that prepares next image
	# - provide config menu callback or find a way to add config menus
	# - somehow inform if we need to pause.
	
	
	# looking for all files in ./plugins/ folder that end with .ini or .rc
	dirlist = os.listdir(appConfig.common['homepath']+'plugins/')
	srch = re.compile('.+\.(ini|rc)$',re.IGNORECASE)
	plugins = []
	for item in dirlist:
		if srch.search(item):
			plugins.append(item)
	
	# now the plugins should be populated with all detected .ini and .rc files.
	if plugins:	# if it is...
		# the next part requires understanding of what each key is used for.
		# see file.ini, or webimage.ini in plugins folder for explanation of each.
		# to sum it up, all we really care are 2 things: exec and protocol entries.
		# - exec is the py file that actually contains our plugin.
		# - protocol is an identifyer, a nickname for that plugin.
		# actual filenames don't have to match protocol name, but it is nice to have.
		# so you can have webimage-John-edition.ini, pointing to myftpaccess.py and registered as johnsimages protocol.
		# once a link in 'resourcePath' is assigned to johnsimages plugin, it will have a "johnsimages://asfd.asdf" url and
		# we will use that "johnsimages" as the catch-phrase to see when to use the plugin.
		os.chdir(appConfig.common['homepath']) # without this, import will not work for me. Even changing PYTHONPATH in mid-script doesn't help.
		cfg = localconf.cfgObject()
		for item in plugins:
			cfg.load(appConfig.common['homepath']+'plugins/'+item)
			protocol = cfg.get('init','protocol') # that's our made up protocol like "flickr" or "pixma", as well as "file" and "http"
			if debug():
				print "in Plugins: item is ", item, '\nProtocol and Config are: ', protocol, cfg
			if protocol: # meaning, if there is an actual entry of "protocol" in section "init", otherwise it's None type
				module = cfg.get('init','exec') # this will be the python file which we will treat as module
				# cuttin out ".py" from the file name. Import can't handle those.
				if module[-3:] == '.py':
					module = module[:-3]
				exec "import plugins."+module  # this should look like "import plugins.flickr" in the end
				exec "appConfig.plugins['"+protocol+"'] = plugins."+module+".pluginClass()"
				# I didn't want to make plugin authors import re module, so I am doing it here.
				appConfig.plugins[protocol].filterObj = re.compile(appConfig.plugins[protocol].filterStr)

	# now, we will do something rude
	# "http" urls are special, and if someone tries to be funny and registers
	# that as a plugin protocol, we kick them into gonads.
	if appConfig.plugins.has_key('http'):
		appConfig.plugins.__delitem__('http')

	return appConfig.plugins.keys()

###########################################################################
# INSTANCE CONFIGURATION
###########################################################################
def instanceConfig(widget):
	global appConfig, _
	
	# this will be a temporary config instance for theme.
	instance = instanceClass()
	# mirroring instance settings into global registry, 
	# You can continue to use "instance" until the end of this function,
	# and use appConfig.instance[widget] in all functions you call from here with widget as argument.
	# you don't need to pass theme settings themselves.
	appConfig.instance[widget] = instance
	
	# Creates empty images for shadow (sombra) framame (estilo) and photo (foto)
	# They are filled with real images later. Coordinates are not important
	instance.ptr['foto'] = karamba.createImage(widget,0,0,appConfig.common['temasPath']+"null.png")
	#this is just to show the right pointer when hovering over.
	karamba.attachClickArea(widget, instance.ptr['foto'])

	instance.ptr['sombra'] = []
	instance.ptr['estilo'] = []
	for i in range(8): # we get [0-7], no 8
		instance.ptr['sombra'].append(karamba.createImage(widget,1,i,appConfig.common['temasPath']+"null.png"))
		instance.ptr['estilo'].append(karamba.createImage(widget,2,i,appConfig.common['temasPath']+"null.png"))	
	
	instance.cfg.update(appConfig.newInstanceSettings())
	# newInstanceSettings(widget) will read the setings from next available instance profile
	# or insert defaults if there is no available profile.
	# see theme class above for the list of all values.
	instance.makeSettingsProper() # see help for this function. Making relevant strings into int's.
	
	createMenus(widget)

	# move to the right location.
	karamba.moveWidget(widget, instance.cfg['locationX'], instance.cfg['locationY'])
	
	if debug():
		print "in InstanceConfig: instance is ", instance.cfg

	# next function takes care of determining the source type, and assigning a new image.
	# If it returns True , we have an image. It exists and instance[widget].cfg['currentPath'] is set.
	# if not, we have to put in the default image and draw that.
	if not tryNextImage(widget):
		# hmmm... the image in theme.cfg['resourcePath'] is not available or nothing meaningfull is there.
		# we will overwrite "currentPath' with default image and keep resourcePath intact.
		# NEED_WORK show some indication that image was not accepted.
		instance.cfg['currentPath'] = appConfig.common['temasPath']+'inicio.png'
	
	# We do not DRAW the instance in this function, only configuring.
	# Calling function draws all needed elements.

###########################################################################
# TRY NEXT IMAGE
###########################################################################
def tryNextImage(widget, customPath=None):
	'''
	tryNextImage(widget, [customPath]):
	
	This function will test the validity of provided customPath, or if it's empty,
	will try to get a next image out of the resourcePath entry.
	
	If both fail, the nothing is changed and False is returned.
	Calling function should figure out what to do in that case.
	
	The new path, will be written into the related instance[widget].cfg[currentPath].
	
	This function returns True if instance[widget].cfg[currentPath] WAS CHANGED.
	It returns False if customPath is rejected or there is a problem with pre-set path.
	
	RESULTING customPath MUST BE LOCAL PATH, NOT "file://" FORMAT, AS IT MAY CRASH SUPERKARAMBA.
	IF PATH IS NOT MADE LOCAL, YOUR IMAGES MAY BE !!!!! DELETED !!!!! IF SUPERKARAMBA CRASHES BEFORE TELLING
	THE SYSTEM D-N-D WAS A SUCCESS.
	'''
	
	global appConfig, _
	
	# for ease of use, lets make a shortcut to the settings dictionary
	instance = appConfig.instance[widget]

	if customPath: # if it is given, it is likely dropped or otherwise newlly entered RAW URL
		testPath = customPath
	else:
		testPath = instance.cfg['resourcePath']

	# looking for string followed by ://
	if re.search('^\w+(?=://)', testPath):
		protocol = re.match('^\w+(?=://)', testPath).group(0)
	else:
		protocol = None
		# and we will be kicked out by the next IF because None is not a supported protocol.

	if debug():
		print "tryNextImage: protocol and testpath: ", protocol, testPath

	# Normally we would need to convert dropped-in RAW, real urls into "internal" urls
	# so, in cases when customPath is given, we "convert" it, but, there are special cases
	# which are considered even before we start matching a url to a plugin.
	# special case scenarios: file://, or http:// ending in image extension.
	# these go directly to file and webcam plugins provided with afoto without asking any questions.

	# I chose these "priority" cases arbitrarily for full-proof operation,
	# fearing that bad filters of custom plugins would mistakenly detect these paths as theirs and greatly annoy the user.
	# Feel free to fold them into generic plugin detection agorythm... at your peril! Tam tam tam taaaammm!
	
	success = False
	
	if appConfig.plugins.has_key(protocol):
		# This is the right place to explain why we are 
		# bothering with registering of custom url protocols like myfancyprotocol://dir/picture
		# If "Bad" plugin filters are used bellow, they may start detecting all
		#  http://asdf/asdf URLs as theirs.
		# Asking user which of many aspiring plugins user wants to use is only nice when done
		# once per drop, not on every reload.
		if appConfig.plugins[protocol].tryNextImage(widget, instance, appConfig.common, customPath):
			success = True
	elif protocol == 'http' and appConfig.common['fileFilter'].search(testPath):
		# this is a special case, I am forcefully overstepping other plugins with this one.
		# for all paths with "http://" that end in image-like extension.
		# (see memo bellow) If custom plugin filters ARE sane, this special case will be removed.
		# or, I can implement some sort of priority ranking for plugins.
		customPath = appConfig.plugins['webimage'].translatePath(customPath)
		if appConfig.plugins['webimage'].tryNextImage(widget, instance, appConfig.common, customPath):
			success = True
	else:
		# In this part we try to use regular expression signatures provided by each plugin
		# to guess which plugin to use.
		# in other words, you will be here when user drops in a new path.
		# Lets see if we can detect the plugin with custom filters.
		# we can catch more than one plugin if the author gave a too simple filter.
		# so we will collect all of the matching ones, and if more than one, we ask user which he wants
		detected = []
		for plugin in appConfig.plugins.keys():
			if appConfig.plugins[plugin].filterObj.search(customPath):
				detected.append(plugin)
		if detected.__len__() >= 0:
			# TODO: here we should ask user which one of many cought protocols user wants.
			# but at this time just select the 1st.
			protocol = detected[0]
			customPath = appConfig.plugins[protocol].translatePath(customPath)
			if appConfig.plugins[protocol].tryNextImage(widget, instance, appConfig.common, customPath):
				success = True

	return success
#
###########################################################################


###########################################################################
# These just makes it easier to send messages through kdialog.
#
def notificationWindow(message, title="", mode=None):
	'''
	This uses KDE kdialog app and asks for main body message and window title.
	App name is attached automatically
	'''
	global appConfig
	myCommand = 'kdialog --caption "'+appConfig.common['name']+'" --title "'+title+'" --msgbox "'+message+'"'
	karamba.execute(myCommand)
#
##########################################################

################## Config Menu Creations / Handling ####################
# Config Menu: creation and handling of menu actions.
#
def createMenus(widget):
	'''
	Creates menu that you get from right-clicking on the widget.
	This is part of layout rutine, include in karamba's init.
	'''
	global appConfig, _
	
	# lets make a shortcut for ease of use.
	menu = appConfig.instance[widget].menu # it's a dict type object now.
	
	# this is the old style menu
	karamba.addMenuConfigOption(widget, "about", _("About ")+appConfig.common['name']+" ...")
	karamba.setMenuConfigOption(widget, "about", 0)
	
	# These are alternatives to the traditional superkaramba "Configure Theme" menu.
	# These will work with Unicode in pre-0.40 versions of SuperKaramba (which has the unicode in menu fixed)
	menu['separator'] = []
	menu['menu1'] = karamba.createMenu(widget)
	menu['view'] = karamba.addMenuItem(widget, menu['menu1'], _("Open image in viewer") , "viewmag")
	menu['explore'] = karamba.addMenuItem(widget, menu['menu1'], _("Show image location") , "bookmark_folder")
	menu['separator'].append(karamba.addMenuSeparator(widget, menu['menu1']))
	menu['configure'] = karamba.addMenuItem(widget, menu['menu1'], _("Configure")+" "+appConfig.common['name'] , "configure")
	menu['separator'].append(karamba.addMenuSeparator(widget, menu['menu1']))
	if appConfig.instance[widget].cfg['update']:
		menu['pause'] = karamba.addMenuItem(widget, menu['menu1'], _("Pause slideshow") , "player_pause")
	else:
		menu['pause'] = karamba.addMenuItem(widget, menu['menu1'], _("Resume slideshow") , "player_play")
	
	menu['menu2'] = karamba.createMenu(widget)
	menu['frame'] = karamba.addMenuItem(widget, menu['menu2'], _("Choose frame style...") , "frame_image")
	menu['shadowsize'] = karamba.addMenuItem(widget, menu['menu2'], _("Set shadow size...") , "transform")
	menu['maxsize'] = karamba.addMenuItem(widget, menu['menu2'], _("Set maximum photo size...") , "crop")
	menu['updateinterval'] = karamba.addMenuItem(widget, menu['menu2'], _("Set slideshow interval...") , "history")
	menu['save'] = karamba.addMenuItem(widget, menu['menu2'], _("Save settings"), "filesave")
	if appConfig.instance[widget].cfg['limitlong']:
		menu['limitlong'] = karamba.addMenuItem(widget, menu['menu2'], _("Limit by longer side") , "apply")
	else:
		menu['limitlong'] = karamba.addMenuItem(widget, menu['menu2'], _("Limit by longer side") , appConfig.common['temasPath']+"null.png")

# Reading of old-style menu
def menuOptionChanged(widget, key, value):
	"This function is called when right-click Karamba menu is clicked."
	global appConfig, _
	if key=="about":
		notificationWindow(appConfig.common['name']+'\n'+appConfig.common['version']+'\n'+_('Created by')+':\n'+appConfig.common['author']+'\n'+_('For latest version search kde-look.org for ')+appConfig.common['name'], _("About ")+appConfig.common['name'])
		karamba.setMenuConfigOption(widget, "about", 0)

# Reading of new-style menu
def menuItemClicked(widget, menu, id):
	global appConfig, _
	
	# creating a shortcut for ease of use.
	instance = appConfig.instance[widget]
	
	if (menu == instance.menu['menu1']):
		if(id == instance.menu['view']): # Open image in viewer
			karamba.execute('kfmclient exec "'+instance.cfg['currentPath']+'"')
		elif(id == instance.menu['explore']): # show location in file manager
			karamba.execute('konqueror --select "'+instance.cfg['currentPath']+'"')
		elif(id == instance.menu['configure']): # Configure Menu
			karamba.popupMenu(widget, instance.menu['menu2'], 5, 5)
		elif(id == instance.menu['pause']):
			if instance.cfg['update']:
				instance.pause(widget)
			elif (instance.cfg['update'] == 0) and (instance.cfg['currentPath'] != instance.cfg['resourcePath']):
				instance.unpause(widget)

	elif (menu == instance.menu['menu2']):
		if(id == instance.menu['frame']): # frame decor dialog
			ventana = ["kdialog", "--caption", appConfig.common['name'], "--title", _("Configure"), "--menu", _("Select new frame style")]
			# Creates a list of folders with temas inside.
			temasList = []
			temp = os.listdir(appConfig.common['temasPath'])
			try:
				temp.sort()
			except:
				pass
			for i in range(len(temp)):
				if os.path.isdir(appConfig.common['temasPath']+temp[i]) and temp[i][0]!='.':
					temasList.append(temp[i])
					temasList.append(temp[i]) # the 2nd time we do it for "kdialog --menu" which takes essentially a dict type as argument.
			instance.ptr['frame'] = karamba.executeInteractive(widget, ventana + temasList)
		elif(id == instance.menu['save']): # Save settings
			instance.cfg['locationX'], instance.cfg['locationY'] = karamba.getWidgetPosition(widget)
			instance.pushSettingsDown()
			appConfig.ini.save()
		elif(id == instance.menu['shadowsize']): # shadow width dialog
			ventana = ["kdialog",  "--caption", appConfig.common['name'], "--title", _("Configure"), "--inputbox", _("Set the width of shadows (pixels)"),str(instance.cfg['shadowsize'])]
			instance.ptr['shadowsize'] = karamba.executeInteractive(widget, ventana)
		elif(id == instance.menu['maxsize']): # Maximum foto size dialog
			ventana = ["kdialog", "--caption", appConfig.common['name'], "--title", _("Configure"), "--inputbox", _("Set the maximum size for your picture (pixels)"),str(instance.cfg['maxsize'])]
			instance.ptr['maxsize'] = karamba.executeInteractive(widget, ventana)
		elif(id == instance.menu['updateinterval']): # update interval
			ventana = ["kdialog", "--caption", appConfig.common['name'], "--title", _("Configure"), "--inputbox", _("Set the refresh rate (minutes)"), str(int(instance.cfg['updateinterval']/60000))]
			instance.ptr['updateinterval'] = karamba.executeInteractive(widget, ventana)
		elif(id == instance.menu['limitlong']): # limit by longer / shorter side
			if instance.cfg['limitlong']: # if it's now True, change to False
				instance.cfg['limitlong'] = 0
				karamba.removeMenuItem(widget, instance.menu['menu2'], instance.menu['limitlong'])
				instance.menu['limitlong'] = karamba.addMenuItem(widget, instance.menu['menu2'],  _("Limit by longer side") , appConfig.common['temasPath']+"null.png")
			else: # if it's now False, change to True
				appConfig.instance[widget].cfg['limitlong'] = 1
				karamba.removeMenuItem(widget, instance.menu['menu2'], instance.menu['limitlong'])
				instance.menu['limitlong'] = karamba.addMenuItem(widget, instance.menu['menu2'], _("Limit by longer side") , "apply")
			redrawObjects(widget)
	
	# cleaning up the shortcut, cause it feels good
	instance = None

def commandOutput(widget, pid, output):
	'''
	This gets called when a command you have executed with executeInteractive()
	outputs something to stdout. This way you can get the output of for example
	kdialog without freezing up the widget waiting for kdialog to end.
	 widget = reference to your theme
	 pid = process number of the program outputting
	 output = the text the program outputted to stdout
	'''
	global appConfig
	
	# creating a shortcut for ease of use.
	instance = appConfig.instance[widget]
	
	if (pid == instance.ptr['maxsize']) and (output != "\n"):
		instance.cfg['maxsize'] = int(output.replace('\n',''))
		redrawObjects(widget)
		instance.pushSettingsDown()
		appConfig.ini.save()
		instance.ptr['maxsize'] = None
	elif (pid == instance.ptr['shadowsize']) and (output != "\n"):
		instance.cfg['shadowsize'] = int(output.replace('\n',''))
		loadTema(widget)
		redrawObjects(widget)
		instance.pushSettingsDown()
		appConfig.ini.save()
		instance.ptr['shadowsize'] = None
	elif (pid == instance.ptr['updateinterval']) and (output != "\n"):
		instance.cfg['updateinterval'] = int(output.replace('\n',''))*60000 # we get answer in minutes
		instance.setNewInterval(widget)
		instance.pushSettingsDown()
		appConfig.ini.save()
		instance.ptr['updateinterval'] = None
	elif (pid == instance.ptr['frame']) and (output != "\n"):
		instance.cfg['frame'] = output.replace('\n','')
		loadTema(widget)
		redrawObjects(widget)
		instance.pushSettingsDown()
		appConfig.ini.save()
		instance.ptr['frame'] = None
#
########################################################################

################## LOAD TEMA IMAGES ####################
#  This function only loads the frame pieces.
#  This is done to separate (and not to waste cycles / harddrive time on) loading frame pieces every time a foto in slide show changes
#  This function need to have appConfig.tema already set to NEW theme.
#  This function also SETS "do not draw" flags to frame / shadow, when needed.
def loadTema(widget):
	global appConfig
	lista = glob.glob( appConfig.common['temasPath'] + appConfig.instance[widget].cfg['frame'] + "/t_*.png")
	if len(lista) == 8:
		appConfig.instance[widget].flag['drawFrame'] = True
		for i in range(8):
			karamba.setImagePath(widget, appConfig.instance[widget].ptr['estilo'][i], appConfig.common['temasPath']+appConfig.instance[widget].cfg['frame']+"/t_"+appConfig.common['frameParts'][i]+".png")
	else:
		appConfig.instance[widget].flag['drawFrame'] = False
		for i in range(8):
			karamba.setImagePath(widget, appConfig.instance[widget].ptr['estilo'][i], appConfig.common['temasPath']+"null.png")

	lista = glob.glob( appConfig.common['temasPath'] + appConfig.instance[widget].cfg['frame'] + "/s_*.png")
	if appConfig.instance[widget].cfg['shadowsize'] > 0 and len(lista) == 8:
		appConfig.instance[widget].flag['drawShadow'] = True
		for i in range(8):
			karamba.setImagePath(widget, appConfig.instance[widget].ptr['sombra'][i], appConfig.common['temasPath']+appConfig.instance[widget].cfg['frame']+"/s_"+appConfig.common['frameParts'][i]+".png")
	else:
		appConfig.instance[widget].flag['drawShadow'] = False
		for i in range(8):
			karamba.setImagePath(widget, appConfig.instance[widget].ptr['sombra'][i], appConfig.common['temasPath']+"null.png")
#
####################################################


################## LOAD FOTO AND CHANGE FRAME SIZE ACCORDIGNLY ##############
#  This function does NOT load the frame pieces or tests for their presence.
#  all it does is: changes foto, resizes it, and all ather EXISTING elements.
#  instance cfg should be set up and will be red from.
#
def redrawObjects(widget):
	global appConfig, _
	
	# lets get our usuall shortcut going
	instance = appConfig.instance[widget]
	
	########################################################################
	# 1st step of smart relocation routine - getting the "center" stored
	#	need to check if separate frame drowing will not brake this.
	#  -0.25 is there to remove the rounding bias towards higher numbers
	#
	# widgetHeight and widgetWidth are hard to find out quickly, and impossible
	# to calculate once a new image is loaded. So, we are storing them in the
	# config over foto update cycles. We use that to calculate coordinates offset.
	########################################################################	
	
	widgetX, widgetY = karamba.getWidgetPosition(widget)
	widgetX = widgetX + int(round((instance.cfg['width']/2)-0.25))
	widgetY = widgetY + int(round((instance.cfg['height']/2)-0.25))
	# Now we have coordinates of widget center

	########################################################################
	# Exif-based autorotation.
	# Part 1. Find out if new image needs other angle and pre-rotate the image.
	# Note, we are rotating IMAGE CANVAS, not particular image.
	# You can reload images in pre-rotated canvas all day, it will still be rotated.
	# in other words, New image does not reset the rotation angle.
	########################################################################	
	
	# this is not used anywhere yet, but will be needed for translations eventually.
	menuoption = _("EXIF-based auto-rotation")
	
	newangle = 0 # rotate to 12 o'clock, if needed.
	flipDimensions = False
	if re.compile(".+\.(jpg|jpeg)$",re.IGNORECASE).search(instance.cfg['currentPath']):
		# ok, dealing with jpeg, lets see if we have EXIF info
		orientationFlag = getoutput('identify -format "orientation=%[EXIF:Orientation]" "'+ appConfig.encode(instance.cfg['currentPath'])+'"')
		if debug():
			print "redrawObjects: this is Jpeg\nredrawObjects: rotation start, EXIF output is ", orientationFlag, type(instance.cfg['currentPath'])
		orientFilter = re.compile('(?<=orientation=)[68]{1}(?=\s*$)') 
		if orientFilter.search(orientationFlag): # looking for 6, or 8 at the end of string, following "orientation="
			orientationFlag = int(orientFilter.search(orientationFlag).group(0))
			# now, it's just a number. 6, or 8. And we need it in degrees offset.
			newangle = (7-orientationFlag)*90 # 6 = 90 degree position, 8 = -90 (270) position
			flipDimensions = True

	if instance.flag['lastorientation'] != newangle:
		# we are going to load small image and rotate that to 0 degrees position, insted of large old image.
		# as of version 0.41 superkaramba used a real rotate algorythm which was very slow.
		# if superkaramba learns to "flip" images to 90, 180, 270, we will stop loading and rotating null.
		karamba.setImagePath(widget, instance.ptr['foto'], appConfig.common['temasPath']+'null.png')
		karamba.rotateImage(widget, instance.ptr['foto'], newangle)
		instance.flag['lastorientation'] = newangle
		if debug():
			print "redrawObjects: rotation end"


	########################################################################
	# Loading anctual image
	########################################################################	
	# NEED_WORK
	# implement test of SUCCESSFULL loading of image.
	# will be important when start supporting network images.
	karamba.setImagePath(widget, instance.ptr['foto'], instance.cfg['currentPath'])
	
	# getting actual image size.
	fotoWidth,fotoHeight = karamba.getImageSize(widget,instance.ptr['foto'])


	if debug():
		print "redrawObjects here: path, width, height ", instance.cfg['currentPath'], fotoWidth,fotoHeight
	
	########################################################################
	# EXIF-Based rotation
	# Part 2. we need to flip the image dimentions 
	# We couldn't do it before because we didn't know the dimensions
	########################################################################	
	if flipDimensions:
		fotoWidth, fotoHeight = fotoHeight, fotoWidth
		# the end of rotation.
	
	if debug():
		print "redrawObjects: resizing image start"

	########################################################################
	# Resizing
	########################################################################	
	if fotoWidth and fotoHeight: # if there was a loading error sizes are 0s
		# for the following to work we need "from __future__ import division"
		ratio = fotoHeight / fotoWidth # if > 1, tall, is < 1 wide
		# if this is not logical, try simplifying all scenarios and you will get this.
		# For fun bonus point, it is possible simplify it further through use of bool(ratio) and limitlong as powers for ratio.
		# Try it. But, then, "free rides" are tougher to isolate.
		if bool(int(ratio)) == instance.cfg['limitlong']: # 0 = 0, and 1 = 1 cases
			if fotoHeight > instance.cfg['maxsize']: # if not, no need to resize - free ride.
				fotoHeight = instance.cfg['maxsize']
				fotoWidth = int(instance.cfg['maxsize'] / ratio)
				karamba.resizeImageSmooth(widget, instance.ptr['foto'], fotoWidth, fotoHeight)
		else: # 1 = 0, and 0 = 1 case
			if fotoWidth > instance.cfg['maxsize']: # if not, no need to resize - free ride.
				fotoHeight = int(instance.cfg['maxsize'] * ratio)
				fotoWidth = instance.cfg['maxsize']
				karamba.resizeImageSmooth(widget, instance.ptr['foto'], fotoWidth, fotoHeight)
		
		if debug():
			print "redrawObjects: resizing image end\nredrawObjects: new fotoWidth, fotoHeight, ratio ", fotoWidth, fotoHeight,ratio
	
	widgetWidth = fotoWidth
	widgetHeight = fotoHeight
	# Now we have "tweaked" fotoWidth and fotoHeight and can use them for other things.
	
	if debug():
		print "redrawObjects: resizing frame start"
	
	# RESIZING FRAME PIECES
	width0, height0, width7, height7 = 0,0,0,0 # these relate to frame pieces 0 upper left, and 7 lower right
	if instance.flag['drawFrame'] == True:
		# we will use the corner pieces as guides for all other sizes.
		# (we need 2 corners because one will not work for "non-square" frames like "a-foto")
		# piece 0
		width0,height0 = karamba.getImageSize(widget,instance.ptr['estilo'][0]) # upper left corner piece
		# and piece 7
		width7,height7 = karamba.getImageSize(widget,instance.ptr['estilo'][7]) # lower right corner piece
		# piece 2 is unchanged
		# piece 5 is unchanged
		# we are only changing the long frame pieces
		karamba.resizeImageSmooth(widget, instance.ptr['estilo'][1], fotoWidth, height0)
		karamba.resizeImageSmooth(widget, instance.ptr['estilo'][3], width0, fotoHeight)
		karamba.resizeImageSmooth(widget, instance.ptr['estilo'][4], width7, fotoHeight)
		karamba.resizeImageSmooth(widget, instance.ptr['estilo'][6], fotoWidth, height7)
		widgetWidth = widgetWidth+width0+width7
		widgetHeight = widgetHeight+height0+height7
	
	# RESIZING SHADOW PIECES
	if instance.flag['drawShadow'] == True:
		karamba.resizeImageSmooth(widget, instance.ptr['sombra'][0], instance.cfg['shadowsize'], instance.cfg['shadowsize'])
		karamba.resizeImageSmooth(widget, instance.ptr['sombra'][2], instance.cfg['shadowsize'], instance.cfg['shadowsize'])
		karamba.resizeImageSmooth(widget, instance.ptr['sombra'][5], instance.cfg['shadowsize'], instance.cfg['shadowsize'])
		karamba.resizeImageSmooth(widget, instance.ptr['sombra'][7], instance.cfg['shadowsize'], instance.cfg['shadowsize'])
		karamba.resizeImageSmooth(widget, instance.ptr['sombra'][1], widgetWidth, instance.cfg['shadowsize'])
		karamba.resizeImageSmooth(widget, instance.ptr['sombra'][3], instance.cfg['shadowsize'], widgetHeight)
		karamba.resizeImageSmooth(widget, instance.ptr['sombra'][4], instance.cfg['shadowsize'], widgetHeight)
		karamba.resizeImageSmooth(widget, instance.ptr['sombra'][6], widgetWidth, instance.cfg['shadowsize'])
		widgetWidth = widgetWidth+instance.cfg['shadowsize']*2
		widgetHeight = widgetHeight+instance.cfg['shadowsize']*2
	
	if debug():
		print "redrawObjects: resizing frame end"

	# RESIZING THE WIDGET
	karamba.resizeWidget(widget, widgetWidth, widgetHeight)

	########################################################################
	# MOVING ALL THE PIECES
	########################################################################	
	
	if debug():
		print "redrawObjects: moving pieces start"
		
	# we need this when tema contains NO files for shadow.
	if instance.flag['drawShadow'] == False:
		shadowsize = 0
	else:
		shadowsize = instance.cfg['shadowsize']
	
	# MOVING FOTO
	karamba.moveImage(widget,instance.ptr['foto'], width0+shadowsize, height0+shadowsize)
	# it has to be here because moving resets Tips
	karamba.addImageTooltip(widget, instance.ptr['foto'], _("- Drop image file or folder here.\n- (Double-)Click for options and settings."))
	
	# MOVING FRAME PIECES
	if instance.flag['drawFrame']:
		karamba.moveImage(widget,instance.ptr['estilo'][0], 0+shadowsize, 0+shadowsize)
		karamba.moveImage(widget,instance.ptr['estilo'][1], width0+shadowsize, 0+shadowsize)
		karamba.moveImage(widget,instance.ptr['estilo'][2], fotoWidth+width0+shadowsize, 0+shadowsize)
		karamba.moveImage(widget,instance.ptr['estilo'][3], 0+shadowsize, height0+shadowsize)
		karamba.moveImage(widget,instance.ptr['estilo'][4], fotoWidth+width0+shadowsize, height0+shadowsize)
		karamba.moveImage(widget,instance.ptr['estilo'][5], 0+shadowsize, fotoHeight+height0+shadowsize)
		karamba.moveImage(widget,instance.ptr['estilo'][6], width0+shadowsize, fotoHeight+height0+shadowsize)
		karamba.moveImage(widget,instance.ptr['estilo'][7], fotoWidth+width0+shadowsize, fotoHeight+height0+shadowsize)
	
	# MOVING SHADOW PIECES
	if instance.flag['drawShadow']:
		karamba.moveImage(widget,instance.ptr['sombra'][0], 0, 0)
		karamba.moveImage(widget,instance.ptr['sombra'][1], shadowsize, 0)
		karamba.moveImage(widget,instance.ptr['sombra'][2], width7+fotoWidth+width0+shadowsize, 0)
		karamba.moveImage(widget,instance.ptr['sombra'][3], 0, shadowsize)
		karamba.moveImage(widget,instance.ptr['sombra'][4], width7+fotoWidth+width0+shadowsize, shadowsize)
		karamba.moveImage(widget,instance.ptr['sombra'][5], 0, height7+fotoHeight+height0+shadowsize)
		karamba.moveImage(widget,instance.ptr['sombra'][6], shadowsize, height7+fotoHeight+height0+shadowsize)
		karamba.moveImage(widget,instance.ptr['sombra'][7], width7+fotoWidth+width0+shadowsize, height7+fotoHeight+height0+shadowsize)	
	
	if debug():
		print "redrawObjects: moving pieces end"
	
	########################################################################
	# 2nd step of smart relocation routine - getting new "center" recalculated and applied
	########################################################################	
	
	if instance.cfg['width'] and instance.cfg['height']:
		if debug():
			print "redrawObjects: moving widget to new place"
		widgetX = widgetX - int(round((widgetWidth/2)-0.25))
		widgetY = widgetY - int(round((widgetHeight/2)-0.25))
		karamba.moveWidget(widget, widgetX, widgetY)
		instance.cfg['locationX'] = widgetX # these two are saved here becase widgetClosed() gives me grief.
		instance.cfg['locationY'] = widgetY # so, now I am forced to save it every cycle.
	
	instance.cfg['width'] = widgetWidth
	instance.cfg['height'] = widgetHeight
	instance.pushSettingsDown()
	appConfig.ini.save()
	
	# finally, redraw
	karamba.redrawWidget(widget)
	if debug():
		print "redrawObjects: checking location vars ", instance.cfg['locationX'], instance.cfg['locationY'], instance.cfg['width'], instance.cfg['height']
		print "redrawObjects: actual location reported is ", karamba.getWidgetPosition(widget)
		print "redrawObjects: Finished loading foto.\n\n"

##################################################################
# KARAMBA's built-in functions
##################################################################

#this is called when you widget is initialized
def initWidget(widget):
	global appConfig
	
	# 1st we need to figure out if there is another a-foto in the same thread of SuperKaramba
	try:
		if appConfig.common['name']: # we do it by checking if this var already exists
			# this means other shared instances of a-foto are running.
			# good, we are starting is a good, correct way.
			# next step will be to activate a new a-foto instance
			if debug():
				print "Starting that applet, yet appConfig already exists.\nProbably another instance"
			pass
	except:
		# This means this instance of a-foto is 1st in this SuperKaramba thread
		# but, there may be other SK threads, so we have to test for that.
		# if we are really 1st Afoto - we run the rest of afoto.
		appConfig = configClass()
		appConfig.ini = localconf.cfgObject()
		
		# commonConfig takes care of testing for other SK threads that may contain afoto
		# (later, i should take the testing out of commonConfig and make it a separate function for ease of code reading)
		# scanPlugins does exactly that, assuming we return from commonConfig with no errors.
		if commonConfig(widget) and scanPlugins():
			appConfig.common['cleanStart'] = True

	# we should ONLY get to this point if cleanStart = True, but I would still like to test for it.
	# if commonConfig() above fails, we never get here.
	if appConfig.common['cleanStart']:
		instanceConfig(widget)
		loadTema(widget) # loads the frame images.
		loadImg = karamba.getThemeImage(widget, "loading")
		karamba.deleteImage(widget,loadImg)
		redrawObjects(widget) # loads foto and arranges already loaded frame pieces around it.
		appConfig.ini.delayed_action() # it might have been set to "save" by tryNextImage(), and was set by sure by redrawObjects if widget location changed.
		karamba.acceptDrops(widget)
	else:
		loadImg = karamba.getThemeImage(widget, "loading")
		karamba.setImagePath(widget, loadImg, 'error.png')
		karamba.changeInterval(widget, 1000000)
		karamba.redrawWidget(widget)

#this is called everytime your widget is updated
#the update inverval is specified in the .instance file
def widgetUpdated(widget):
	global appConfig
	if appConfig.instance[widget].cfg['update']:
		if tryNextImage(widget):
			redrawObjects(widget)

#This is called when your widget is closed.  You can use this to clean
#up open files, etc.  You don't need to delete text and images in your
#theme.  That is done automatically.  This callback is just for cleaning up
#external things.  Most people don't need to put anything here.
def widgetClosed(widget):
    # we will only save widget position here. If there was a "widgetMoved" I would use that instead.
	# since the actual position of the widget is derivative from center of current image, we also need to save last width, height
	# (those are NOT saved in slideshow mode) We save those dimmensions as part of instance.pushSettingsDown()
	# INI file is opened and immediately closed on every write so we dont worry about closing it.
	# all other settings are saved when changed.
	if debug():
		print "closing, widget number is ", widget
	try:
		if appConfig.common['cleanStart']:
			appConfig.instance[widget].pushSettingsDown()
			# As a curtesy, we also demote ourselves in the Order line.
			# demote returns the instance's new place in the Order array.
			# if it is 0 - we are the last running instance.
			# If you run 3 a-foto and exit SuperKaramba, you need to save settigs 3 times.
			# this way we only save once.
			# we could also just check if common['active'] == [], but it would not be as fun.
			if not appConfig.demoteInstance(appConfig.instance[widget].cfg['associatedName']):
				# demote pushSettingsDown() the relevant part, so we just save.
				appConfig.ini.save()
				os.remove(appConfig.common['homepath']+'running')
	except:
		pass

def itemDropped(widget, dropText, x=0, y=0):
	global appConfig, _
	
	if debug():
		print "in the itemDrag dropText is ", dropText, type(dropText)

	if tryNextImage(widget, dropText): # returns True if anything was changed.
		redrawObjects(widget)
		appConfig.instance[widget].pushSettingsDown()
		appConfig.ini.save()
	else:
		# create some sort of feedback when image is not accepted
		pass


#This gets called everytime our widget is clicked.
#Notes:
#  widget = reference to our widget
#  x = x position (relative to our widget)
#  y = y position (relative to our widget)
#  botton = button clicked:
#                    1 = Left Mouse Button
#                    2 = Middle Mouse Button
#                    3 = Right Mouse Button, but this will never happen
#                        because the right mouse button brings up the
#                        Karamba menu.
#                    4,5 = Scroll wheel up and down
def widgetClicked(widget, x, y, button):
	#btnPressed(widget, x, y, button)
	#pass
	global appConfig
	if(button == 1):
		karamba.popupMenu(widget, appConfig.instance[widget].menu['menu1'], x, y)
	elif(button == 2):
		karamba.popupMenu(widget, appConfig.instance[widget].menu['menu2'], x, y)

#This gets called everytime mouse is moved over our widget.
#Notes
#  widget = reference to our widget
#  x = x position (relative to our widget)
#  y = y position (relative to our widget)
#  botton = button being held:
#                    0 = No Mouse Button
#                    1 = Left Mouse Button
#                    2 = Middle Mouse Button
#                    3 = Right Mouse Button, but this will never happen
#                        because the right mouse button brings up the
#                        Karamba menu.
def widgetMouseMoved(widget, x, y, button):
    #Warning:  Don't do anything too intensive here
    #You don't want to run some complex piece of code everytime the mouse moves
    pass

#This gets called when a meter (image, text, etc) is clicked.
# NOTE you must use attachClickArea() to make a meter
# clickable.  
#  widget = reference to your theme
#  meter = the meter clicked
#  botton = button clicked:
#                    1 = Left Mouse Button
#                    2 = Middle Mouse Button
#                    3 = Right Mouse Button, but this will never happen
#                        because the right mouse button brings up the
#                        Karamba menu.
#                    4,5 = Scroll wheel up and down
# "kfmclient exec \""+valor_origen+"\"","konqueror --select \""+valor_origen+"\"")
def meterClicked(widget, meter, button):
	pass

# This will be printed when the widget loads.
print "Afoto is loaded."
